#!/usr/bin/python
# -*- coding: utf-8 -*-
# AUTHOR:   yemd
# FILENAME: py_redis_mq.py
# DESCRIBE: redis消息队列封装库
# CREATED:  2014-01-20 10:20
# MODIFIED: 2014-02-11 13:48

import inspect
import time
import redis
import socket
from dm_py_util import *


RECONNECT_TIME = 30 * 2000000
#RECONNECT_TIME = 60*3


##########################################
#
#redis message queue封装库
#
##########################################
class PY_Redis_MQ():

    def __init__(self, Host='127.0.0.1', Port=6379, SocketTimeout=5, BlockTimeout=2):
        self.__host = Host
        self.__port = Port
        self.__socket_timeout = SocketTimeout  # socket过期时间#
        self.__block_timeout = BlockTimeout  # 阻塞式弹出命令(brpop,blpop)
        self.__rediscontext = None
        self.__last_active_time = 0.0
        self.__password = None
        self.__cn__ = self.__class__.__name__  # classname,for log debug

    def init(self, config):
        _fn_ = inspect.currentframe().f_code.co_name
        try:
            cfgparser = PYC_ConfigParser(config)
            self.__host = cfgparser.get_config("mqconfig", "address")
            self.__port = int(cfgparser.get_config("mqconfig", "port"))
            self.__socket_timeout = int(cfgparser.get_config(
                "mqconfig", "sockettimeout"))
            self.__block_timeout = int(cfgparser.get_config(
                "mqconfig", "blocktimeout"))
            if self.__host is None:
                raise Exception("config error::address is None")
            if self.__port <= 0 or self.__socket_timeout <= 0:
                raise Exception("config error::port<=0 or timeout<=0")
            if self.__block_timeout < 0:
                raise Exception("config error::blocktimeout<0")
            DEBUG_LOG(_fn_, "host-port", self.__host, self.__port)
            DEBUG_LOG(_fn_, "init redis_mq success!!!!!!!!!!!!!!!!!!!")
        except Exception, e:
            raise DM_InputError(_fn_, str(e))
        try:
            self.__password = cfgparser.get_config('mqconfig', 'password')
        except Exception as e:
            WARN_LOG(_fn_, 'get redis password error')

    def __release(self):
        _fn_ = inspect.currentframe().f_code.co_name
        #DEBUG_LOG(_fn_,"start!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        if self.__rediscontext:
            DEBUG_LOG(_fn_, "set rediscontext None!!!!!")
            self.__rediscontext = None

    def __ping(self):
        _fn_ = inspect.currentframe().f_code.co_name
        #DEBUG_LOG(_fn_,"start!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        if self.__rediscontext is None:
            raise DM_InnerError(_fn_, "rediscontext is None")
        try:
            self.__rediscontext.ping()
        except Exception, e:
            raise DM_RedisError(self.__cn__, _fn_, str(e))

    def __connect(self):
        _fn_ = inspect.currentframe().f_code.co_name
        #DEBUG_LOG(_fn_,"start!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        try:
            #释放redis的所有上下文
            self.__release()

            #设置长链接选项
            keepalive_options = {}
            #1.开启keepalive属性
            keepalive_options[socket.SO_KEEPALIVE] = 1
            #2.若连接在60s内无任何数据往来,则进行探测
            keepalive_options[socket.TCP_KEEPIDLE] = 60
            #3.探测时发包的时间间隔为5s
            keepalive_options[socket.TCP_KEEPINTVL] = 5
            #4.探测次数
            keepalive_options[socket.TCP_KEEPCNT] = 3

            #构建redis连接上下文
            self.__rediscontext = redis.Redis(
                host=self.__host,
                port=self.__port,
                socket_timeout=self.__socket_timeout,
                socket_keepalive=True,
                socket_keepalive_options=keepalive_options,
                password=self.__password)
            # 用ping查看是否能成功连接服务器
            self.__ping()
            #更新活跃时间
            self.__last_active_time = time.time()
        except Exception, e:
            self.__rediscontext = None
            raise DM_RedisError(_fn_, str(e))

    """
    # function：支持多条mess同时进入queue中[lpush]
    # return：  失败抛出异常，成功则进入队列
    # 用法：    put_mess_to_queue(key,m1,m2,m3)
    """

    def put_mess_to_queue(self, queuekey, *mess):
        _fn_ = inspect.currentframe().f_code.co_name
        #DEBUG_LOG(_fn_,"start+++++++++++++++++++++++++++++++++")
        if not isinstance(queuekey, str) or not queuekey:
            raise DM_InputError(_fn_, "args queuekey error", str(queuekey))
        if not mess:
            raise DM_InputError(_fn_, "mess is None")
        lasttime = time.time() - self.__last_active_time
        if self.__rediscontext is None or lasttime >= RECONNECT_TIME:
            try:
                self.__connect()
            except Exception, e:
                raise DM_RedisError(_fn_, str(e))
        #DEBUG_LOG(_fn_,"key-mess",queuekey,str(mess))
        try:
            res = self.__rediscontext.lpush(queuekey, *mess)
            #print _fn_,res,type(res)
            self.__last_active_time = time.time()
            #DEBUG_LOG(_fn_,"put mess success!!!!!!!!!!!!!!!!")
        except Exception, e:
            raise DM_RedisError(_fn_, "[lpush] cmd error", str(e))

    """
    # function：支持阻塞方式从多个key队列获取消息数据[brpop]
    # return：  失败抛出异常，成功直接返回元组类型，格式如下：
    #            (queuekey,message) queuekey队列名,message队列消息
    # 用法：    get_mess_from_queue([k1,k2,k3])
    """

    def get_mess_from_queue(self, keylist):
        _fn_ = inspect.currentframe().f_code.co_name
        #DEBUG_LOG(_fn_,"start+++++++++++++++++++++++++++++++++")
        if not isinstance(keylist, list) or not keylist:
            raise DM_InputError(_fn_, "args keylist error", str(keylist))
        lasttime = time.time() - self.__last_active_time
        if self.__rediscontext is None or lasttime >= RECONNECT_TIME:
            try:
                self.__connect()
            except Exception, e:
                raise DM_RedisError(_fn_, str(e))
        #DEBUG_LOG(_fn_,"keylist",keylist)
        try:
            #brpop使用:
            # 1. N个队列,按0-N-1,优先读第一个非空队列,直到读空才读下个非空队列;
            #    一旦所有队列读空,等待timeout时间阻塞,等待队列的新数据;
            #timeout参数使用：
            # 1. 默认不设置(timeout=0),阻塞时间可以无限期延长;
            # 2. 一旦设置timeout=2则只阻塞2s,仍然无数据则返回None;
            # 3. 另注:一旦连接时设置了socket_timeout,则必须大于block_timeout;
            #     否则socket过期时间一到(未设置socket无过期时间)，若仍然无数据
            #     将报错Error while reading from socket:('timed out',)
            result = self.__rediscontext.brpop(keylist, self.__block_timeout)
            #DEBUG_LOG(_fn_,"brpop result",str(result))
            self.__last_active_time = time.time()
            if result is None:
                #WARN_LOG(_fn_,"no data in queues")
                return ()
            else:
                return result
        except Exception, e:
            raise DM_RedisError(_fn_, "[brpop] cmd error", str(e))

    """
    # function：非阻塞方式从指定队列获取消息数据[rpop]
    # return：  失败抛出异常，成功直接返回队列消息(str格式)
    # 用法：    get_mess_from_queue_noblock(queuekey)
    """

    def get_mess_from_queue_noblock(self, queuekey):
        _fn_ = inspect.currentframe().f_code.co_name
        #DEBUG_LOG(_fn_,"start+++++++++++++++++++++++++++++++++")
        if not isinstance(queuekey, str) or not queuekey:
            raise DM_InputError(_fn_, "args queuekey error", str(queuekey))
        lasttime = time.time() - self.__last_active_time
        if self.__rediscontext is None or lasttime >= RECONNECT_TIME:
            try:
                self.__connect()
            except Exception, e:
                raise DM_RedisError(_fn_, str(e))
        #DEBUG_LOG(_fn_,"queuekey",queuekey)
        try:
            result = self.__rediscontext.rpop(queuekey)
            #DEBUG_LOG(_fn_,"rpop result",str(result))
            self._last_active_time = time.time()
            return result
        except Exception, e:
            raise DM_RedisError(_fn_, "[rpop] cmd error", str(e))

    def get_redis(self):
        _fn_ = inspect.currentframe().f_code.co_name
        lasttime = time.time() - self.__last_active_time
        if self.__rediscontext is None or lasttime >= RECONNECT_TIME:
            try:
                self.__connect()
            except Exception, e:
                raise DM_RedisError(_fn_, str(e))
        return self.__rediscontext


if __name__ == '__main__':
    SET_LOG_KEY("messqueue")
    configfile = '../mq.config'
    _fn_ = "main"

    #mq库使用方式#
    DEBUG_LOG("configfile", configfile)
    mqinterface = PY_Redis_MQ()
    try:
        mqinterface.init(configfile)
    except Exception, e:
        ERROR_LOG(_fn_, 'mqinterface init err', str(e))
        exit(-1)

    #插入两个队列的数据#
    key1 = 'A'
    v11 = 'aaaaaa'
    v12 = 'bbbbbb'
    v13 = 'cccccc'
    v14 = 'dddddd'
    try:
        mqinterface.put_mess_to_queue(key1, v11, v12, v13, v14)
    except Exception, e:
        ERROR_LOG(_fn_, str(e))
        exit(-1)

    key2 = 'B'
    v21 = '111111'
    v22 = '222222'
    v23 = '333333'
    try:
        mqinterface.put_mess_to_queue(key2, v21, v22, v23)
    except Exception, e:
        ERROR_LOG(_fn_, str(e))
        exit(-1)

    #阻塞方式获取数据：循环读取多个队列数据,直到所有队列数据读空#
    times = 0
    while 1:
        times = times + 1
        back = {}
        try:
            back = mqinterface.get_mess_from_queue(["NO_EXIST_QUEUE"])
            print "1", _fn_, back, type(back)
            back = mqinterface.get_mess_from_queue([key1, key2])
            print "2", _fn_, back, type(back)
            back = mqinterface.get_mess_from_queue([key1, key2])
            print "3", _fn_, back, type(back)
        except Exception, e:
            ERROR_LOG(_fn_, str(e))
            exit(-1)
        if times >= 3:
            WARN_LOG(_fn_, "break::times", times)
            break

    #非阻塞方式获取指定队列内容
    try:
        back = mqinterface.get_mess_from_queue_noblock(key1)
        print "1", _fn_, back, type(back)
        back = mqinterface.get_mess_from_queue_noblock(key2)
        print "2", _fn_, back, type(back)
    except Exception, e:
        ERROR_LOG(_fn_, str(e))
        exit(-1)

    exit(0)
